package com.ho1ho.audioexample.utils;

import android.media.AudioRecord;
import android.media.MediaRecorder;
import android.media.audiofx.AcousticEchoCanceler;
import android.media.audiofx.AutomaticGainControl;
import android.media.audiofx.NoiseSuppressor;
import android.util.Log;

import com.ho1ho.audioexample.CustomApplication;
import com.ho1ho.audioexample.others.Callback;
import com.webrtc.audioprocessing.ApmViewModel;
import com.webrtc.audioprocessing.AudioProcessing;
import com.webrtc.audioprocessing.Counters;

import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.util.Objects;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * Created by Michael Leo <y@ho1ho.com><br>
 * Date: 2020/02/11 09:06
 * <p>
 * Code is far away from bug with the animal protecting
 * </p>
 * <pre>
 * ----------Dragon be here!----------/
 *    ┏┓   ┏┓
 *   ┏┛┻━━━┛┻┓
 *   ┃       ┃
 *   ┃   ━   ┃
 *   ┃ ┳┛ ┗┳ ┃
 *   ┃       ┃
 *   ┃   ┻   ┃
 *   ┃       ┃
 *   ┗━┓   ┏━┛
 *     ┃   ┃神兽保佑
 *     ┃   ┃代码无BUG！
 *     ┃   ┗━━━┓
 *     ┃       ┣┓
 *     ┃       ┏┛
 *     ┗┓┓┏━┳┓┏┛
 *      ┃┫┫ ┃┫┫
 *      ┗┻┛ ┗┻┛
 * ━━━━━━神兽出没━━━━━━
 * </pre>
 */
public class MicRecord implements Counters {
    private static final String TAG = MicRecord.class.getSimpleName();

    private ExecutorService mThreadPool = Executors.newFixedThreadPool(1);
    private boolean mIsRecording;
    private AacEncoder mAacEncoder;
    private AudioRecord mAudioRecord;

    private int mSampleRate;
    //    private int mBitrate;
//    private int mChannelCount;
    private int mChannelMask;
    private int mAudioFormat;
    private int mRecordBufferSize;

    // We ask for a native buffer size of BUFFER_SIZE_FACTOR * (minimum required
    // buffer size). The extra space is allocated to guard against glitches under
    // high load.
    private static final int BUFFER_SIZE_FACTOR = 1;
    private ApmViewModel apmViewModel;
    private AudioProcessing audioProcessing;

    public MicRecord(int sampleRate, int bitrate, int channelCount, int channelMask, int audioFormat) {
        this(sampleRate, bitrate, channelCount, channelMask, audioFormat,
                AudioRecord.getMinBufferSize(sampleRate, channelMask, audioFormat) * 2);
    }

    public MicRecord(int sampleRate, int bitrate, int channelCount, int channelMask, int audioFormat, int recordBufferSize) {
        Log.e(TAG, "recordBufferSize=" + recordBufferSize);
        mSampleRate = sampleRate;
//        mBitrate = bitrate;
//        mChannelCount = channelCount;
        mChannelMask = channelMask;
        mAudioFormat = audioFormat;
        mRecordBufferSize = recordBufferSize;

        mAacEncoder = new AacEncoder(sampleRate, bitrate, channelCount);

        createAudioRecord();
        initAdvancedFeatures();

        try {
            apmViewModel = new ApmViewModel();

            apmViewModel.setSpeechIntelligibilityEnhance(true);
            apmViewModel.setSpeaker(true);

            // ACE
            apmViewModel.setAecMobile(false);
            apmViewModel.setAecPC(true);
            apmViewModel.setAecPCMode0(false);
            apmViewModel.setAecPCMode1(false);
            apmViewModel.setAecPCMode2(true);
            apmViewModel.setAceBufferDelay("150");
            apmViewModel.setDelayAgnostic(true);

            // AGC
            apmViewModel.setAgc(true);
            apmViewModel.setAgcMode0(false);
            apmViewModel.setAgcMode1(true);
            apmViewModel.setAgcMode2(false);
            apmViewModel.setAgcTargetLevel("6"); // [0, 31]
            apmViewModel.setAgcCompressionGain("9"); // [0, 90]

            // NS
            apmViewModel.setNs(true);
            apmViewModel.setNsMode0(false);
            apmViewModel.setNsMode1(false);
            apmViewModel.setNsMode2(true);
            apmViewModel.setNsMode3(false);

            // VAD
            apmViewModel.setVad(true);

        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    private void createAudioRecord() {
        mAudioRecord = new AudioRecord(
//                MediaRecorder.AudioSource.MIC,
                MediaRecorder.AudioSource.VOICE_COMMUNICATION,
                mSampleRate,
                mChannelMask,
                mAudioFormat,
                mRecordBufferSize); // https://blog.csdn.net/lavender1626/article/details/80394253
    }

    private void initAdvancedFeatures() {
        if (AcousticEchoCanceler.isAvailable()) {
            AcousticEchoCanceler aec = AcousticEchoCanceler.create(mAudioRecord.getAudioSessionId());
            if (aec != null) {
                aec.setEnabled(true);
            }
        }

        if (AutomaticGainControl.isAvailable()) {
            AutomaticGainControl agc = AutomaticGainControl.create(mAudioRecord.getAudioSessionId());
            if (agc != null) {
                agc.setEnabled(true);
            }
        }

        if (NoiseSuppressor.isAvailable()) {
            NoiseSuppressor nc = NoiseSuppressor.create(mAudioRecord.getAudioSessionId());
            if (nc != null) {
                nc.setEnabled(true);
            }
        }
    }

    public void stop() {
        mIsRecording = false;

        if (null != mAudioRecord) {
            try {
                mAudioRecord.stop();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    public void release() {
        stop();

        if (null != mAudioRecord) {
            try {
                mAudioRecord.release();
            } catch (Exception e) {
                e.printStackTrace();
            }
            mAudioRecord = null;
        }

        if (mAacEncoder != null) {
            try {
                mAacEncoder.close();
            } catch (Exception e) {
                e.printStackTrace();
            }
            mAacEncoder = null;
        }

        if (audioProcessing != null) {
            audioProcessing.onDestroy();
            audioProcessing = null;
        }

        if (mThreadPool != null) {
            mThreadPool.shutdownNow();
        }
    }

    public void doRecord(Callback callback) {
        mAudioRecord.startRecording();
        mIsRecording = true;

        audioProcessing = new AudioProcessing(CustomApplication.instance.getApplicationContext(), apmViewModel, this);
        audioProcessing.onSpeaker(true);

        mThreadPool.execute(() -> {
            final int bytesPerFrame = AudioProcessing.CHANNELS * (AudioProcessing.BITS_PER_SAMPLE / 8);
            final int framesPerBuffer = AudioProcessing.SAMPLE_RATE / AudioProcessing.BUFFERS_PER_SECOND;
            final int recordSize = bytesPerFrame * framesPerBuffer / 2;

            short[] apmPcmData = new short[recordSize];

            String outputFolder = Objects.requireNonNull(CustomApplication.instance.getExternalFilesDir(null)).getAbsolutePath() + File.separator + "leo-audio";
            File folder = new File(outputFolder);
            if (!folder.exists()) {
                folder.mkdirs();
            }
            File pcmFile = new File(outputFolder, "original.pcm");
            File pcmApmFile = new File(outputFolder, "original-apm.pcm");
            File aacFile = new File(outputFolder, "original.aac");
            final String pcmFilename = pcmFile.getAbsolutePath();
            final String pcmApmFilename = pcmApmFile.getAbsolutePath();
            final String aacFilename = aacFile.getAbsolutePath();

            byte[] aacAudioData;
            long st;
            try (BufferedOutputStream pcmOs = new BufferedOutputStream(new FileOutputStream(pcmFilename));
                 BufferedOutputStream pcmApmOs = new BufferedOutputStream(new FileOutputStream(pcmApmFilename));
                 BufferedOutputStream aacOs = new BufferedOutputStream(new FileOutputStream(aacFilename))) {
                int readSize;
                while (mIsRecording) {
                    readSize = mAudioRecord.read(apmPcmData, 0, apmPcmData.length);

                    if (readSize > 0) {
                        byte[] apmPcmDataInBytes = new byte[readSize * 2];
                        for (int i = 0; i < apmPcmData.length; i++) {
                            apmPcmDataInBytes[i * 2] = (byte) (apmPcmData[i] & 0xFF);
                            apmPcmDataInBytes[i * 2 + 1] = (byte) (apmPcmData[i] >>> 8 & 0xFF);
                        }
                        pcmOs.write(apmPcmDataInBytes);

//                        audioProcessing.process(apmPcmData, readSize);

                        apmPcmDataInBytes = new byte[readSize * 2];
                        for (int i = 0; i < apmPcmData.length; i++) {
                            apmPcmDataInBytes[i * 2] = (byte) (apmPcmData[i] & 0xFF);
                            apmPcmDataInBytes[i * 2 + 1] = (byte) (apmPcmData[i] >>> 8 & 0xFF);
                        }
                        pcmApmOs.write(apmPcmDataInBytes);

//                        st = SystemClock.elapsedRealtime();
                        aacAudioData = mAacEncoder.encodePcmToAac(apmPcmDataInBytes);
//                            Log.e("time", "Encode cost " + (SystemClock.elapsedRealtime() - st));
                        aacOs.write(aacAudioData);

                        callback.onCallback(aacAudioData);
                    }
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        });
    }

    public boolean isRecording() {
        return mIsRecording;
    }

    // === APM - Counter start =======================
    @Override
    public void sendCount(int count) {
        Log.d(TAG, "apm send counter=" + count);
    }

    @Override
    public void receivedCount(int count) {
        Log.d(TAG, "apm recv counter=" + count);
    }
    // === APM - Counter end =======================
}
